/*
 * NmeaParser.cpp
 *
 *  Created on: 27.6.2011.
 *      Author: Hetcheem
 */

#include "NmeaParser.h"

#include <sstream>
#include <string>

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <math.h>
#include <string.h>

NmeaParser* NmeaParser::pInstance = NULL;

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

NmeaParser::NmeaParser() : m_state(SearchForStartOfSentence) 
{
	m_checksumStr[2] = '\0';				// checksum has only 2 chars	
}

NmeaParser* NmeaParser::GetParser()
{
	if (pInstance == NULL)
	{
		pInstance = new NmeaParser();
	}
	return pInstance;
}

void NmeaParser::Parse(const char *buffer, const unsigned buffSize) 
{
	/*
	Each message's starting character is a dollar sign.
	The next five characters identify the talker (two characters) and the type of message (three characters).
	All data fields that follow are comma-delimited.
	Where data is unavailable, the corresponding field contains NUL bytes (e.g., in "123,,456", the second field's data is unavailable).
	The first character that immediately follows the last data field character is an asterisk, but it is only included if a checksum is supplied.
	The asterisk is immediately followed by a two-digit checksum representing a hexadecimal number. The checksum is the exclusive OR of all characters between the $ and *. According to the official specification, the checksum is optional for most data sentences, but is compulsory for RMA, RMB, and RMC (among others).
	<CR><LF> ends the message: carriage return linefeed (ASCII: 13, 10)
*/

	// Parse all characters
    for (unsigned i = 0; i < buffSize; i++)
	{
		const char& ch = buffer[i];
		
        if (m_state == SearchForStartOfSentence) 
		{
            if (ch == '$') 
			{
				m_state = RetrieveSentence;
                m_calcChecksum = 0;
				m_NMEASequenceIndex = 0;
			    m_NMEASequence[m_NMEASequenceIndex++] = ch;
            }
        }
        else 
		{
            if (m_NMEASequenceIndex == NMEA_SEQUENCE_MAX_LENGTH - 1) 
			{
		        m_state = SearchForStartOfSentence;
                continue;
            }
            m_NMEASequence[m_NMEASequenceIndex++] = ch;

            if (m_state == RetrieveSentence) 
			{
			    if (ch == '*') 
				{
				    m_state = GetChecksumCharacters;
                    m_checksumIndex = 0;
                }
			    else if (ch == '\r')
				    m_state = WaitForSentenceTermination;
			    else
				    m_calcChecksum ^= ch;
            }
            else if (m_state == GetChecksumCharacters)
			{
			    if (!isxdigit(ch))
				    m_state = SearchForStartOfSentence;
			    else 
				{
				    m_checksumStr[m_checksumIndex] = ch;
				    if (m_checksumIndex++) 
					{
					    if (axtoi(m_checksumStr) == m_calcChecksum)
						    m_state = WaitForSentenceTermination;
					    else
						    m_state = SearchForStartOfSentence;
				    }
                }
            }
            else if (m_state == WaitForSentenceTermination) 
			{
			    if (ch == '\n') 
				{
				    m_NMEASequence[m_NMEASequenceIndex] = '\0';
				    ParseNMEASentence(m_NMEASequence);	
				    m_state = SearchForStartOfSentence;			
			    }
				else if (ch != '\r')
					m_state = SearchForStartOfSentence;
            }
        }
    }
}

int NmeaParser::ParseNMEASentence(char *buffer) {
 /*
 * "GP***"	Talker ID (GP for a GPS unit (US), GL for a GLONASS (Russian))
*/   
    char *type = NULL;
	char buffCopy[81+2+1];
	strcpy(buffCopy, buffer);
	type = strtok(&buffCopy[1], ","); // skip '$'
    
////////////////////////////////////////////////////////
#ifdef GPSDATA_H_
    if (strcmp(type, "GPGGA") == 0)
		return m_GPSData.ProcessGPGGA(buffer);
    if (strcmp(type, "GPRMC") == 0)
        return m_GPSData.ProcessGPRMC(buffer);
#endif /* GPSDATA_H_ */

////////////////////////////////////////////////////////
#ifdef DTWX3DATA_H_
    if (strcmp(type, "DTWX3") == 0)
	    return m_DTWX3Data.ProcessDTWX3(buffer);
#endif /* DTWX3DATA_H_ */
    
////////////////////////////////////////////////////////
#ifdef AVX03DATA_H_
    if (strcmp(type, "AVX03") == 0)
        return m_AVX03Data.ParseSentence(buffer);
#endif /* AVX03DATA_H_ */

////////////////////////////////////////////////////////
#ifdef PCX03DATA_H_
    if (strcmp(type, "PCX03") == 0)
        return m_PCX03Data.ParseSentence(buffer);
#endif /* PCX03DATA_H_ */

	return 0;
}

/*
 * isxdigit()
 *
 * Checks if character is valid hexadecimal character/digit.
 *
 * @param c: Character to be checked, casted to an int, or EOF.
 * @return: A value different from zero (i.e., true) if indeed c is a hexadecimal character/digit. Zero (i.e., false) otherwise.
 */

int NmeaParser::isxdigit(const int& c)
{
	return isdigit(c) || (toupper(c) >= 'A' && toupper(c) <= 'F');
}

/*
 * axtoi()
 *
 * Transforms hexadecimal character string to integer.
 *
 * @param str: C string beginning with the representation of an hexadecimal number.
 * @return: Integer representation of hexadecimal string
 *
 * @note: Case insensitive.
 */
int NmeaParser::axtoi(const char *hexStg) 
{
	int n = 0;         // position in string
	int m = 0;         // position in digit[] to shift
	int count;         // loop index
	int intValue = 0;  // integer value of hex string
	int digit[5];      // hold values to convert
	while (n < 4)
	{
		if (hexStg[n]=='\0')
			break;
		if (hexStg[n] > 0x29 && hexStg[n] < 0x40 ) //if 0 to 9
			digit[n] = hexStg[n] & 0x0f;            //convert to int
		else if (hexStg[n] >='a' && hexStg[n] <= 'f') //if a to f
			digit[n] = (hexStg[n] & 0x0f) + 9;      //convert to int
		else if (hexStg[n] >='A' && hexStg[n] <= 'F') //if A to F
			digit[n] = (hexStg[n] & 0x0f) + 9;      //convert to int
		else break;
		n++;
	}
	count = n;
	m = n - 1;
	n = 0;
	while(n < count)
	{
		intValue = intValue | (digit[n] << (m << 2));
		m--;   // adjust the position to set
		n++;   // next digit to process
	}
	return (intValue);
}

